using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Linq;

namespace Faml.Operations
{

    public interface ICacheable
    {
        bool IsCacheable { get; }
        string CacheKey { get; }

    }

	public abstract class LazyReference<T> 
    {
		private WeakReference valuesRef = new WeakReference(null);

		public T Values
		{
			get
			{
				T values = (T)valuesRef.Target;
				if (values == null)
				{
					values = GetValues();
					valuesRef = new WeakReference(values);
				}
				return values;	
			}
		}

		protected abstract T GetValues();

        internal abstract Expression Expression { get; }        
	}

    public class Scalar<T> : LazyReference<T?>
        where T : struct
    {
        private Expression<Func<T>> expr;

        internal Scalar(Expression<Func<T>> expr)
        {
            this.expr = expr;
        }

        internal override Expression Expression
        {
            get
            {
                return expr.Body;
            }
        }

        protected override T? GetValues()
        {
            Func<T> f = expr.Compile();
            return f();
        }

        public static implicit operator Scalar<T>(T value)
        {
            return new Scalar<T>(() => value);
        }

        public static implicit operator T(Scalar<T> value)
        {
            return value.Values.Value;
        }

        public static Scalar<T> operator +(Scalar<T> n, Scalar<T> m)
        {
            Expression ex_plus = Expression.Add(n.Expression, m.Expression);

            return new Scalar<T>((Expression<Func<T>>)Expression.Lambda(ex_plus));
        }

        public static Scalar<T> operator -(Scalar<T> n, Scalar<T> m)
        {
            Expression ex_plus = Expression.Subtract(n.Expression, m.Expression);

            return new Scalar<T>((Expression<Func<T>>)Expression.Lambda(ex_plus));
        }

        public static Scalar<T> operator *(Scalar<T> n, Scalar<T> m)
        {
            Expression ex_plus = Expression.Multiply(n.Expression, m.Expression);

            return new Scalar<T>((Expression<Func<T>>)Expression.Lambda(ex_plus));
        }

        public static Scalar<T> operator /(Scalar<T> n, Scalar<T> m)
        {
            Expression ex_plus = Expression.Divide(n.Expression, m.Expression);

            return new Scalar<T>((Expression<Func<T>>)Expression.Lambda(ex_plus));
        }

        public static Scalar<T> operator %(Scalar<T> n, Scalar<T> m)
        {
            Expression ex_plus = Expression.Modulo(n.Expression, m.Expression);

            return new Scalar<T>((Expression<Func<T>>)Expression.Lambda(ex_plus));
        }
    }

    public class Array1d<T> : LazyReference<T[]>
	{
        private Expression<Func<T[]>> expr;

        internal Array1d(Expression<Func<T[]>> expr)
        {
            this.expr = expr;
        }

        internal override Expression Expression
        {
            get
            {
                return expr.Body;
            }
        }

        protected override T[] GetValues()
        {
            Func<T[]> f = expr.Compile();
            return f();
        }

        public static implicit operator Array1d<T>(T[] array)
        {
            return new Array1d<T>(() => array);
        }
    }

    public class Array2d<T> : LazyReference<T[,]>
    {
        private Expression<Func<T[,]>> expr;

        internal Array2d(Expression<Func<T[,]>> expr)
        {
            this.expr = expr;
        }

        internal override Expression Expression
        {
            get
            {
                return expr.Body;
            }
        }

        protected override T[,] GetValues()
        {
            Func<T[,]> f = expr.Compile();
            return f();
        }

        public static implicit operator Array2d<T>(T[,] array)
        {
            return new Array2d<T>(() => array);
        }
    }

    public class Array3d<T> : LazyReference<T[, ,]>
    {
        private Expression<Func<T[, ,]>> expr;

        internal Array3d(Expression<Func<T[, ,]>> expr)
        {
            this.expr = expr;
        }

        internal override Expression Expression
        {
            get
            {
                return expr.Body;
            }
        }

        protected override T[, ,] GetValues()
        {
            Func<T[, ,]> f = expr.Compile();
            return f();
        }

        public static implicit operator Array3d<T>(T[, ,] array)
        {
            return new Array3d<T>(() => array);
        }
    }
}